/**
 * Copyright 2014 ABSir's Studio
 * 
 * All right reserved
 *
 * Create on 2014-2-17 下午3:22:56
 */
package com.absir.server.socket;

import java.io.Serializable;
import java.nio.channels.SocketChannel;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.absir.context.core.ContextUtils;
import com.absir.server.exception.ServerException;
import com.absir.server.exception.ServerStatus;
import com.absir.server.in.InDispatcher;
import com.absir.server.in.InMethod;
import com.absir.server.in.InModel;
import com.absir.server.in.Input;
import com.absir.server.on.OnPut;
import com.absir.server.route.returned.ReturnedResolver;
import com.absir.server.route.returned.ReturnedResolverBody;
import com.absir.server.socket.InputSocket.InputSocketAtt;
import com.absir.server.socket.SocketServerContext.SessionResolver;
import com.absir.server.socket.resolver.SocketSessionResolver;

/**
 * @author absir
 * 
 */
public class SocketReceiverContext extends InDispatcher<InputSocketAtt, SocketChannel> implements SocketReceiver<Serializable> {

	/** serverContext */
	protected ServerContext serverContext;

	/** LOGGER */
	protected static final Logger LOGGER = LoggerFactory.getLogger(SocketReceiverContext.class);

	/** UN_REGISTER_ID */
	public static final Serializable UN_REGISTER_ID = new Character('0');

	/**
	 * @param serverContext
	 */
	public SocketReceiverContext(ServerContext serverContext) {
		this.serverContext = serverContext;
	}

	/**
	 * @return the serverContext
	 */
	public ServerContext getServerContext() {
		return serverContext;
	}

	/**
	 * @return
	 */
	public SocketSessionResolver getSocketSessionResolver() {
		return SessionResolver.ME;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.absir.server.socket.SocketReceiver#acceptTimeout(java.nio.channels
	 * .SocketChannel)
	 */
	@Override
	public long acceptTimeout(SocketChannel socketChannel) throws Throwable {
		if (getSocketSessionResolver().accept(socketChannel, serverContext)) {
			return SocketServerContext.getIdleTimeout();
		}

		return 0;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.absir.server.socket.SocketReceiver#idle(com.absir.server.socket.
	 * SelSession, java.nio.channels.SocketChannel, long)
	 */
	@Override
	public void idle(SelSession selSession, final SocketChannel socketChannel, long contextTime) {
		SocketBuffer socketBuffer = selSession.getSocketBuffer();
		final Serializable id = socketBuffer == null ? null : socketBuffer.getId();
		if (id == null || id == SocketReceiverContext.UN_REGISTER_ID) {
			SocketServer.close(socketChannel);

		} else {
			selSession.retainIdleTimeout(SocketServerContext.getIdleTimeout());
			final IServerContext serverContext = socketBuffer.getServerContext();
			ContextUtils.getThreadPoolExecutor().execute(new Runnable() {

				@Override
				public void run() {
					if (writeBeat(id, socketChannel, serverContext)) {
						getSocketSessionResolver().doBeat(id, socketChannel, serverContext);
					}
				}
			});
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.absir.server.socket.SocketReceiver#register(java.nio.channels.
	 * SocketChannel, com.absir.server.socket.SocketBuffer)
	 */
	@Override
	public void register(final SocketChannel socketChannel, final SelSession selSession) throws Throwable {
		final SocketBuffer socketBuffer = selSession.getSocketBuffer();
		socketBuffer.setId(UN_REGISTER_ID);
		final byte[] buffer = socketBuffer.getBuff();
		socketBuffer.setBuff(null);
		ContextUtils.getThreadPoolExecutor().execute(new Runnable() {

			@Override
			public void run() {
				Serializable id = null;
				try {
					id = getSocketSessionResolver().register(socketChannel, serverContext, buffer, socketBuffer);
					if (id == UN_REGISTER_ID) {
						id = null;
					}

					if (id == null) {
						if (writeFailed(socketChannel)) {
							selSession.retainIdleTimeout(SocketServerContext.getAcceptTimeout());
						}

					} else {
						IServerContext mutilContext = socketBuffer.getServerContext();
						if (writeSuccess(id, socketChannel, mutilContext)) {
							selSession.retainIdleTimeout(SocketServerContext.getIdleTimeout());
							registerSocketChannelContext(mutilContext, selSession, id,
									createSocketChannelContext(id, socketChannel));
							socketBuffer.setId(id);
						}
					}

				} catch (Throwable e) {
					if (SocketServerContext.getCloseDebug()) {
						e.printStackTrace();
					}

					SocketServer.close(socketChannel);

				} finally {
					if (id != null && !socketChannel.isConnected()) {
						try {
							SocketServer.sessionClose(selSession, socketChannel);

						} catch (Throwable e) {
						}
					}
				}
			}
		});
	}

	/**
	 * @param socketChannel
	 * @return
	 */
	protected boolean writeFailed(SocketChannel socketChannel) {
		return InputSocketImpl.writeByteBuffer(socketChannel, SocketServerContext.ME.getOk());
	}

	/**
	 * @param id
	 * @param socketChannel
	 * @param serverContext
	 * @return
	 */
	protected boolean writeSuccess(Serializable id, SocketChannel socketChannel, IServerContext serverContext) {
		return InputSocketImpl.writeByteBuffer(socketChannel, SocketServerContext.ME.getOk());
	}

	/**
	 * @param id
	 * @param socketChannel
	 * @param serverContext
	 * @return
	 */
	protected boolean writeBeat(Serializable id, SocketChannel socketChannel, IServerContext serverContext) {
		return InputSocketImpl.writeByteBuffer(socketChannel, SocketServerContext.ME.getBeat());
	}

	/**
	 * @param mutilContext
	 * @param selSession
	 * @param id
	 * @param socketChannelContext
	 */
	protected void registerSocketChannelContext(IServerContext mutilContext, SelSession selSession, Serializable id,
			SocketChannelContext socketChannelContext) {
		if (mutilContext != null) {
			mutilContext.loginSocketChannelContext(id, socketChannelContext);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.absir.server.socket.SocketReceiver#receiveByteBuffer(java.nio.channels
	 * .SocketChannel, com.absir.server.socket.SelSession)
	 */
	@Override
	public void receiveByteBuffer(final SocketChannel socketChannel, SelSession selSession) throws Throwable {
		final SocketBuffer socketBuffer = selSession.getSocketBuffer();
		final Serializable id = socketBuffer.getId();
		final byte[] buffer = socketBuffer.getBuff();
		socketBuffer.setBuff(null);
		doDenied(socketChannel, id, buffer, socketBuffer);
		if (socketBuffer.addBufferQueue(buffer)) {
			return;
		}

		selSession.retainIdleTimeout(SocketServerContext.getIdleTimeout());
		final IServerContext serverContext = socketBuffer.getServerContext();
		ContextUtils.getThreadPoolExecutor().execute(new Runnable() {

			@Override
			public void run() {
				byte[] queueBuffer = buffer;
				while (true) {
					try {
						if (!doBeat(socketChannel, serverContext, id, queueBuffer, socketBuffer, SocketServerContext.ME.getBeat())) {
							doDispath(socketChannel, id, queueBuffer, socketBuffer);
						}

					} catch (Throwable e) {
						LOGGER.error("queue", e);
					}

					if (!socketChannel.isConnected()) {
						break;
					}

					queueBuffer = socketBuffer.readBufferQueue();
					if (queueBuffer == null) {
						break;
					}
				}
			}
		});
	}

	/**
	 * @param id
	 * @param socketChannel
	 * @return
	 */
	protected SocketChannelContext createSocketChannelContext(Serializable id, SocketChannel socketChannel) {
		SocketChannelContext socketChannelContext = new SocketChannelContext();
		socketChannelContext.setSocketChannel(socketChannel);
		return socketChannelContext;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.absir.server.socket.SocketReceiver#unRegister(com.absir.server.socket
	 * .SelSession, java.io.Serializable, java.nio.channels.SocketChannel)
	 */
	@Override
	public void unRegister(SelSession selSession, Serializable id, SocketChannel socketChannel) throws Throwable {
		IServerContext mutilContext = selSession == null ? null : selSession.getSocketBuffer().getServerContext();
		getSocketSessionResolver().unRegister(id, socketChannel, mutilContext,
				mutilContext == null ? null : mutilContext.logoutSocketChannelContext(id, socketChannel));
	}

	/**
	 * @param socketChannel
	 * @param id
	 * @param buffer
	 * @param socketBuffer
	 */
	protected void doDenied(SocketChannel socketChannel, Serializable id, byte[] buffer, SocketBuffer socketBuffer) {
		if (id == null || id == UN_REGISTER_ID) {
			throw new ServerException(ServerStatus.NO_LOGIN);
		}
	}

	/**
	 * @param buffer
	 * @param socketBuffer
	 * @param beat
	 * @return
	 */
	protected boolean isBeat(byte[] buffer, SocketBuffer socketBuffer, final byte[] beat) {
		int length = beat.length;
		if (buffer.length == length) {
			for (int i = 0; i < length; i++) {
				if (buffer[i] != beat[i]) {
					return false;
				}
			}

			return true;
		}

		return false;
	}

	/**
	 * @param socketChannel
	 * @param serverContext
	 * @param id
	 * @param buffer
	 * @param socketBuffer
	 * @param beat
	 * @return
	 */
	protected boolean doBeat(final SocketChannel socketChannel, final IServerContext serverContext, final Serializable id,
			byte[] buffer, SocketBuffer socketBuffer, final byte[] beat) {
		// 回复心跳
		if (isBeat(buffer, socketBuffer, beat)) {
			ContextUtils.getThreadPoolExecutor().execute(new Runnable() {

				@Override
				public void run() {
					if (getSocketSessionResolver().doBeat(id, socketChannel, serverContext)) {
						writeBeat(id, socketChannel, serverContext);
					}
				}
			});

			return true;
		}

		return false;
	}

	/**
	 * @param socketChannel
	 * @param id
	 * @param buffer
	 * @param socketBuffer
	 */
	protected void doDispath(final SocketChannel socketChannel, Serializable id, byte[] buffer, SocketBuffer socketBuffer) {
		if (buffer.length > 0) {
			byte flag = buffer[0];
			if ((flag & InputSocketAtt.RESPONSE_FLAG) == 0) {
				InputSocketAtt inputSocketAtt = new InputSocketAtt(id, buffer);
				try {
					if (on(inputSocketAtt.getUrl(), inputSocketAtt, socketChannel)) {
						return;
					}

				} catch (Throwable e) {
				}

				InputSocketImpl.writeByteBufferSuccess(socketChannel, false, inputSocketAtt.getCallbackIndex(),
						InputSocket.NONE_RESPONSE_BYTES);

			} else {
				doResponse(socketChannel, id, flag, buffer);
			}
		}
	}

	/**
	 * @param socketChannel
	 * @param id
	 * @param flag
	 * @param buffer
	 */
	protected void doResponse(SocketChannel socketChannel, Serializable id, byte flag, byte[] buffer) {

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.absir.server.in.IDispatcher#getInMethod(java.lang.Object)
	 */
	@Override
	public InMethod getInMethod(InputSocketAtt req) {
		return req.getMethod();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.absir.server.in.IDispatcher#decodeUri(java.lang.String,
	 * java.lang.Object)
	 */
	@Override
	public String decodeUri(String uri, InputSocketAtt req) {
		return uri;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.absir.server.in.InDispatcher#input(java.lang.String,
	 * com.absir.server.in.InMethod, com.absir.server.in.InModel,
	 * java.lang.Object, java.lang.Object)
	 */
	@Override
	protected Input input(String uri, InMethod inMethod, InModel model, InputSocketAtt req, SocketChannel res) {
		InputSocketImpl socketInput = new InputSocketImpl(model, req, res);
		return socketInput;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.absir.server.in.InDispatcher#resolveReturnedValue(java.lang.Object,
	 * com.absir.server.on.OnPut)
	 */
	@Override
	public void resolveReturnedValue(Object routeBean, OnPut onPut) throws Throwable {
		if (onPut.getReturnValue() == null) {
			ReturnedResolver<?> returnedResolver = onPut.getReturnedResolver();
			if (returnedResolver != null && returnedResolver instanceof ReturnedResolverBody) {
				onPut.setReturnValue(InputSocket.NONE_RESPONSE);
			}
		}

		super.resolveReturnedValue(routeBean, onPut);
	}

}
